package cn.xiuxius.embedded.dependencyscan;

import cn.xiuxius.embedded.config.EasyYamlConfig;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class DependencyAnalyzer {

    public static List<KeilFile> analyze(EasyYamlConfig.KeilProject keilProject) throws IOException {
        Path rootPath = Paths.get(keilProject.getRootPath()).toAbsolutePath().normalize();
        List<Path> includeDirs = new ArrayList<>();

        if (keilProject.getIncludes() != null) {
            for (String include : keilProject.getIncludes()) {
                includeDirs.add(rootPath.resolve(include).normalize());
            }
        }

        if (keilProject.getGroups() != null) {
            for (Map<String, EasyYamlConfig.Group> groupMap : keilProject.getGroups()) {
                for (Map.Entry<String, EasyYamlConfig.Group> entry : groupMap.entrySet()) {
                    EasyYamlConfig.Group group = entry.getValue();
                    if (group != null) {
                        Path resolvedPath = rootPath.resolve(group.getPath());
                        if (Files.exists(resolvedPath)) {
                            includeDirs.add(resolvedPath.normalize());
                        }
                    }
                }
            }
        }

        Set<Path> visited = new HashSet<>();
        Map<Path, KeilFile> result = new LinkedHashMap<>();

        Path mainC = findMainC(rootPath);
        if (mainC == null) {
            throw new FileNotFoundException("main.c not found under " + rootPath);
        }
        analyzeFile(mainC, includeDirs, visited, result, rootPath, keilProject);
        addLinkSuffixFiles(result, includeDirs, visited, rootPath, keilProject);
        return new ArrayList<>(result.values());
    }


    public static List<KeilFile> collectLibAndA51Files(EasyYamlConfig.KeilProject keilProject) throws IOException {
        Path rootPath = Paths.get(keilProject.getRootPath()).toAbsolutePath().normalize();
        List<Path> searchDirs = new ArrayList<>();
        if (keilProject.getIncludes() != null) {
            for (String include : keilProject.getIncludes()) {
                searchDirs.add(rootPath.resolve(include).normalize());
            }
        }
        if (keilProject.getGroups() != null) {
            for (Map<String, EasyYamlConfig.Group> groupMap : keilProject.getGroups()) {
                for (Map.Entry<String, EasyYamlConfig.Group> entry : groupMap.entrySet()) {
                    EasyYamlConfig.Group group = entry.getValue();
                    if (group != null) {
                        Path resolvedPath = rootPath.resolve(group.getPath());
                        if (Files.exists(resolvedPath)) {
                            searchDirs.add(resolvedPath.normalize());
                        }
                    }
                }
            }
        }
        Set<KeilFile> result = new LinkedHashSet<>();
        for (Path dir : searchDirs) {
            if (Files.isDirectory(dir)) {
                try (Stream<Path> stream = Files.walk(dir)) {
                    stream.filter(Files::isRegularFile)
                            .filter(p -> {
                                String fileName = p.getFileName().toString().toLowerCase();
                                return fileName.endsWith(FileType.LIB.getSuffix()) || fileName.endsWith(FileType.A51.getSuffix());
                            })
                            .forEach(p -> {
                                FileType type = FileType.fromFileName(p.getFileName().toString());
                                String group = resolveGroup(p, keilProject, rootPath);
                                String relativePath = "./" + rootPath.relativize(p).toString().replace("\\", "/");

                                result.add(new KeilFile()
                                        .setName(p.getFileName().toString())
                                        .setType(type)
                                        .setGroup(group)
                                        .setAbsolutePath(p.toAbsolutePath().toString())
                                        .setPath(relativePath));
                            });
                }
            }
        }
        return new ArrayList<>(result);
    }

    private static void analyzeFile(Path file, List<Path> includeDirs, Set<Path> visited, Map<Path, KeilFile> result,
                                    Path rootPath, EasyYamlConfig.KeilProject keilProject) throws IOException {
        Path normalized = file.toAbsolutePath().normalize();
        if (!visited.add(normalized)) return;

        result.putIfAbsent(normalized, toKeilFile(normalized, rootPath, keilProject));

        List<String> lines = safeReadAllLines(file);
        for (String line : lines) {
            String cleaned = line.replaceAll("//.*", "").replaceAll("/\\*.*?\\*/", "").trim();
            if (!cleaned.startsWith("#include")) continue;

            Matcher matcher = Pattern.compile("#include\\s+[\"<](.*?)[\">]", Pattern.CASE_INSENSITIVE).matcher(cleaned);
            if (matcher.find()) {
                String includeName = matcher.group(1);
                Path includedFile = findFileInIncludes(includeName, includeDirs);
                if (includedFile != null) {
                    analyzeFile(includedFile, includeDirs, visited, result, rootPath, keilProject);

                    if (includedFile.toString().toLowerCase().endsWith(FileType.H.getSuffix())) {
                        String baseName = includedFile.getFileName().toString().replaceAll("\\.h$", "");
                        Path cFile = findFileInIncludes(baseName + FileType.C.getSuffix(), includeDirs);
                        if (cFile != null) {
                            analyzeFile(cFile, includeDirs, visited, result, rootPath, keilProject);
                        }
                    }
                }
            }
        }
    }

    private static Path findFileInIncludes(String fileName, List<Path> includeDirs) {
        for (Path dir : includeDirs) {
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
                for (Path entry : stream) {
                    if (entry.getFileName().toString().equalsIgnoreCase(fileName)) {
                        return entry.toAbsolutePath().normalize();
                    }
                }
            } catch (IOException ignored) {}
        }
        return null;
    }

    private static Path findMainC(Path rootPath) throws IOException {
        try (Stream<Path> stream = Files.walk(rootPath)) {
            return stream
                    .filter(p -> p.getFileName().toString().equalsIgnoreCase("main.c"))
                    .findFirst()
                    .orElse(null);
        }
    }

    private static KeilFile toKeilFile(Path path, Path rootPath, EasyYamlConfig.KeilProject keilProject) {
        String relativePath = "./" + rootPath.relativize(path).toString().replace("\\", "/");
        String name = path.getFileName().toString();
        FileType type = FileType.fromFileName(name);
        String group = resolveGroup(path, keilProject, rootPath);

        return new KeilFile()
                .setName(name)
                .setType(type)
                .setGroup(group)
                .setPath(relativePath)
                .setAbsolutePath(path.toAbsolutePath().toString());
    }

    private static String resolveGroup(Path path, EasyYamlConfig.KeilProject keilProject, Path rootPath) {
        if (keilProject.getGroups() == null) {
            return "unknown";
        }
        for (Map<String, EasyYamlConfig.Group> groupMap : keilProject.getGroups()) {
            for (Map.Entry<String, EasyYamlConfig.Group> entry : groupMap.entrySet()) {
                EasyYamlConfig.Group group = entry.getValue();
                if (group != null) {
                    Path groupPath = rootPath.resolve(group.getPath()).normalize();
                    if (path.startsWith(groupPath)) {
                        return entry.getKey();
                    }
                }
            }
        }
        return "unknown";
    }


    private static void addLinkSuffixFiles(Map<Path, KeilFile> result,
                                           List<Path> includeDirs,
                                           Set<Path> visited,
                                           Path rootPath,
                                           EasyYamlConfig.KeilProject keilProject) throws IOException {
        if (keilProject.getBuild() == null || keilProject.getBuild().getLinkSuffix() == null) return;

        List<String> suffixes = keilProject.getBuild().getLinkSuffix();
        Set<Path> newFiles = new LinkedHashSet<>();

        for (Path existingFile : result.keySet()) {
            String baseName = existingFile.getFileName().toString();
            int dotIndex = baseName.lastIndexOf('.');
            String nameWithoutExt = dotIndex != -1 ? baseName.substring(0, dotIndex) : baseName;
            String extension = dotIndex != -1 ? baseName.substring(dotIndex) : "";

            for (String suffix : suffixes) {
                String candidateName = nameWithoutExt + suffix + extension;
                Path candidate = findFileInIncludes(candidateName, includeDirs);
                if (candidate != null) {
                    Path normalized = candidate.toAbsolutePath().normalize();
                    if (!visited.contains(normalized)) {
                        newFiles.add(normalized);
                    }
                }
            }
        }

        for (Path file : newFiles) {
            analyzeFile(file, includeDirs, visited, result, rootPath, keilProject);
        }
    }


    private static List<String> safeReadAllLines(Path file) throws IOException {
        List<Charset> charsetsToTry = List.of(
                StandardCharsets.UTF_8,
                Charset.forName("GBK"),
                StandardCharsets.ISO_8859_1
        );

        for (Charset charset : charsetsToTry) {
            try {
                return Files.readAllLines(file, charset);
            } catch (MalformedInputException ignored) {
            }
        }
        throw new IOException("Unsupported charset for " + file.toAbsolutePath().normalize() + ": " + charsetsToTry);
    }


}
